/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiIODeviceEpoll.cpp
	Implements non-blocking networking using the Epoll interface found in the
	Linux Kernel.

******************************************************************************/

#include "MaxsiLibrary.h"

#if defined(Maxsi_Support_MaxsiIO) && defined(Maxsi_Support_Epoll)

#ifdef Maxsi_Support_POSIX
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <sys/time.h>
#endif

#include <sys/epoll.h>

MAXSI_TODO("Find a better value for this suggested value for how many concurrent FDs we are going to check. Note, however, this is not a limit, just a guess on how many FDs we need, the kernel performs better if we give it a realistic value.");
const size_t MaxsiIODeviceEpoll::GetEpollListSize() { return 8; }
const size_t MaxsiIODeviceEpoll::GetConcurrentListens() { return 64; }

MaxsiIODeviceEpoll::MaxsiIODeviceEpoll()
{
	EpollFD		=	epoll_create(GetEpollListSize()); // Returns -1 on failure.
}

MaxsiIODeviceEpoll::~MaxsiIODeviceEpoll()
{
	if ( EpollFD != -1 ) { close(EpollFD); }	
}

FLAGSL32 MaxsiIODeviceEpoll::GetCapabilities()
{
	return ( MAXSI_IO_SUPPORTS_NETWORK | MAXSI_IO_SUPPORTS_NON_BLOCKING | MAXSI_IO_SUPPORTS_MAIN_LOOP );
}

MaxsiError MaxsiIODeviceEpoll::MainLoop()
{
	size_t			EventsSize	=	GetConcurrentListens();
	epoll_event*	Events		=	new epoll_event[EventsSize];

	if ( Events == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	while ( true )
	{
		int	NumEvents	=	epoll_wait(EpollFD, Events, EventsSize, -1);

		if ( NumEvents == -1 ) { PrintOutput("Error in epoll_wait!\n"); MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }
	
		if ( NumEvents == 0 ) { PrintOutput("Help! I'm stuck in a busy wait!\n"); }

		for ( int I = 0; I < NumEvents; I++ )
		{
			MaxsiIOHandleEpoll*	Handle	=	(MaxsiIOHandleEpoll*)(Events[I].data.ptr);

			if ( Handle->Device != this )
			{
				PrintOutput("Error: MaxsiIODeviceEpoll can only handle events on connections that use it as the device\n");
			}
			else
			{
				if ( Handle->State & MAXSI_IO_IS_SOCKET_LISTENER )
				{
					/*while ( */AcceptClient(Handle) /*== MAXSI_ERROR_SUCCESS ) { }*/;
				}
				else
				{	
					if ( Events[I].events & EPOLLERR ) { PrintOutput("An error occured on FD=%i.\n", Handle->FD); }
					if ( Events[I].events & EPOLLHUP ) { PrintOutput("A hangup occured on FD=%i.\n", Handle->FD); }
					if ( Events[I].events & EPOLLIN )  { DoRead(Handle); }
					if ( Events[I].events & EPOLLOUT ) { DoWrite(Handle); }
				}
			}
		}
	}

	delete[] Events;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiIODeviceEpoll::HandleOpenDevice(MaxsiIOEntity* Remote, MaxsiIOSocket* Socket, bool Writable)
{
	// This device does not implement this.
	return MAXSI_ERROR_NOT_SUPPORTED;
}

MaxsiError MaxsiIODeviceEpoll::HandleListen(MaxsiIOEntity* Remote, MaxsiIOSocket* Socket, short Port)
{
	if ( Remote == NULL || Socket == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	MaxsiError		Result				=	MAXSI_ERROR_SUCCESS;
	int				SocketFD			=	-1;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiIOHandleEpoll*	Connection		=	new MaxsiIOHandleEpoll;

	// Check if the handle was created.
	Result	=	( Connection == NULL ) ? MAXSI_ERROR_MEM_ALLOC_FAIL : Result;
	
	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		// Reset the connection handle.
		memset(Connection, 0, sizeof(MaxsiIOHandleEpoll));

		// Set the variables.
		Connection->State		=	MAXSI_IO_IS_SOCKET_LISTENER;
		Connection->Device		=	this;
		Connection->Connection	=	Remote;

		// Create a socket.
		SocketFD		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( SocketFD == -1 ) { Result = MAXSI_ERROR_COULD_NOT_CREATE_SOCKET; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set up the local address.
		ServerAddress.sin_family = AF_INET;
		ServerAddress.sin_addr.s_addr = INADDR_ANY;
		ServerAddress.sin_port = htons(Port);

		// Bind the socket to the local address.
		//if ( bind(Socket, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress)) < 0 ) { Result = MAXSI_ERROR_COULD_NOT_BIND_SOCKET; }
		if ( bind(SocketFD, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) < 0 ) { Result = MAXSI_ERROR_COULD_NOT_BIND_SOCKET; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		int flags;

		// Set the socket as non-blocking.
		if ( (flags = fcntl(SocketFD, F_GETFL, 0)) < 0 || fcntl(SocketFD, F_SETFL, flags | O_NONBLOCK) < 0 ) { Result = MAXSI_ERROR_COULD_NOT_SET_NON_BLOCK; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		// Listen on the socket.
		MAXSI_TODO("Find a better hardcoded constant!");
		if ( listen(SocketFD, 5) != 0 ) {	Result = MAXSI_ERROR_COULD_NOT_LISTEN; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		Connection->FD	=	SocketFD;
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		Result	=	SetUpAsyncIOHandleCallbacks((MaxsiIOHandle*)Connection);
	}

	if ( Result == MAXSI_ERROR_SUCCESS ) { if ( SocketFD != -1 ) { close(SocketFD); } }
	if ( Result == MAXSI_ERROR_SUCCESS ) { delete Connection; return NULL; }
	if ( Result != MAXSI_ERROR_SUCCESS ) { *Socket = Connection; } 

	return Result;
}

MaxsiError MaxsiIODeviceEpoll::HandleConnect(MaxsiIOEntity* Remote, MaxsiIOSocket* Socket, MESTR* HostName, short Port)
{
	if ( Remote == NULL || Socket == NULL || HostName == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	MaxsiError		Result				=	MAXSI_ERROR_SUCCESS;
	char*			HostNameSL			=	NULL;
	int				SocketFD			=	-1;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiIOHandleEpoll*		Connection	=	new MaxsiIOHandleEpoll;

	// Check if the handle was created.
	Result	=	( Connection == NULL ) ? MAXSI_ERROR_MEM_ALLOC_FAIL : Result;
	
	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		memset(Connection, 0, sizeof(MaxsiIOHandleEpoll));

		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Result = MAXSI_ERROR_MEM_ALLOC_FAIL; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		// Create a socket.
		SocketFD		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( SocketFD == -1 ) { Result = MAXSI_ERROR_COULD_NOT_CREATE_SOCKET; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		int flags;

		// Set the socket as non-blocking.
		if ( (flags = fcntl(SocketFD, F_GETFL, 0)) < 0 || fcntl(SocketFD, F_SETFL, flags | O_NONBLOCK) < 0 ) { Result = MAXSI_ERROR_COULD_NOT_SET_NON_BLOCK; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);
		MAXSI_TODO("gethostbyname is blocking!");
		MAXSI_TODO("The Open Group doesn't require gethostbyname to be thread safe!");

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Result = MAXSI_ERROR_DNS_LOOKUP_FAILED; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.	
		if ( connect(SocketFD, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) != 0 && errno != EINPROGRESS ) { Result = MAXSI_ERROR_COULD_NOT_CONNECT; }
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		Connection->FD	=	SocketFD;
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		Result	= SetUpAsyncIOHandleCallbacks(Connection);
	}

	IFDEL(HostNameSL);

	if ( Result != MAXSI_ERROR_SUCCESS ) { if ( SocketFD != -1 ) { close(SocketFD); } }
	if ( Result != MAXSI_ERROR_SUCCESS ) { delete Connection; return NULL; }
	if ( Result != MAXSI_ERROR_SUCCESS ) { *Socket = Connection; } 

	return Result;
}

MaxsiError MaxsiIODeviceEpoll::HandleOpenFile(MaxsiIOEntity* Remote, MaxsiIOSocket* Socket, MESTR* Path, FLAGS32 Mode)
{
	// This device does not implement this.
	if ( Socket ) { *Socket = NULL; }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

MaxsiError MaxsiIODeviceEpoll::SetUpAsyncIOHandleCallbacks(MaxsiIOHandle* Connection)
{
	if ( Connection == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	if ( EpollFD == -1 ) { MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }

	epoll_event Event;

	MaxsiIOHandleEpoll*	Handle	=	(MaxsiIOHandleEpoll*)Connection;

	// We can only do this for connections that belong to us.
	if ( Handle->Device != this ) { return MAXSI_ERROR_INVALID_INPUT; }

	memset(&Event, 0, sizeof(Event));

	Event.data.fd	=	Handle->FD;
	Event.data.ptr	=	(void*)Handle;
	Event.events	=	EPOLLERR | EPOLLET | EPOLLIN | EPOLLOUT | EPOLLHUP;

	int	Result = epoll_ctl(EpollFD, EPOLL_CTL_ADD, Handle->FD, &Event);

	if ( Result != 0 ) { PrintOutput("epoll_ctl failed with errno=%i on FD=%i\n", errno, Handle->FD); MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }

	return MAXSI_ERROR_SUCCESS;
}


MaxsiError MaxsiIODeviceEpoll::AttemptRead(MaxsiIOHandle* Connection, BYTE* Buffer, size_t* BufferSize, size_t* Transferred)
{
	if ( Connection == NULL || Buffer == NULL || BufferSize == NULL || Transferred == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	MaxsiIOHandleEpoll*	Handle	=	(MaxsiIOHandleEpoll*)Connection;

	// We can only do this for connections that belong to us.
	if ( Handle->Device != this ) { return MAXSI_ERROR_INVALID_INPUT; }

	*Transferred	=	0;

	while ( *Transferred < *BufferSize )
	{
		ssize_t Read		=	read(Handle->FD, Buffer+*Transferred, *BufferSize - *Transferred);
	
		if ( Read == -1 && errno == EAGAIN ) { break; }
		{
			if ( Read == -1 || Read > SSIZE_MAX ) { return MAXSI_ERROR_COULD_NOT_TRANSFER; }

			if ( Read < 0 ) { Read = 0; } // In the event of an error, say we just transferred 0 bytes during this read.

			*Transferred	+=	Read;
		}
	}	

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiIODeviceEpoll::AttemptWrite(MaxsiIOHandle* Connection, BYTE* Buffer, size_t* BufferSize, size_t* Transferred)
{
	if ( Connection == NULL || Buffer == NULL || BufferSize == NULL || Transferred == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	MaxsiIOHandleEpoll*	Handle	=	(MaxsiIOHandleEpoll*)Connection;

	// We can only do this for connections that belong to us.
	if ( Handle->Device != this ) { return MAXSI_ERROR_INVALID_INPUT; }

	*Transferred	=	0;

	while ( *Transferred < *BufferSize )
	{
		ssize_t Written		=	write(Handle->FD, Buffer+*Transferred, *BufferSize - *Transferred);
	
		if ( Written == -1 && errno == EAGAIN ) { break; }
		{
			if ( Written == -1 || Written > SSIZE_MAX ) { return MAXSI_ERROR_COULD_NOT_TRANSFER; }

			if ( Written < 0 ) { Written = 0; } // In the event of an error, say we just transferred 0 bytes during this write.

			*Transferred	+=	Written;
		}
	}	

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiIODeviceEpoll::HandleAccept(MaxsiIOEntity* Remote, MaxsiIOSocket* Socket, MaxsiIOSocket ListenSocket)
{
	if ( Remote == NULL || Socket == NULL || ListenSocket ) { return MAXSI_ERROR_INVALID_INPUT; }

	if ( *Socket ) { return MAXSI_ERROR_ALREADY_CONNECTED; }

	MaxsiError		Result				=	MAXSI_ERROR_SUCCESS;
	socklen_t		AddrLen				=	0;
	sockaddr		Addr;

	// Create a handle.
	MaxsiIOHandleEpoll*		Connection	=	new MaxsiIOHandleEpoll;

	// Check if the handle was created.
	Result	=	( Connection == NULL ) ? MAXSI_ERROR_MEM_ALLOC_FAIL : Result;
	
	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		memset(Connection, 0, sizeof(MaxsiIOHandleEpoll));

		Connection->Device		=	this;
		Connection->Connection	=	Remote;
		Connection->State		=	MAXSI_EPOLL_IS_SERVER;
	}

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		Connection->FD		=	accept(ListenSocket->FD, (sockaddr*)&Addr, &AddrLen);

		if ( Connection->FD < 0 ) { Result = MAXSI_ERROR_UNSPECIFIED; }
	}
	
	if ( Result != MAXSI_ERROR_SUCCESS ) { delete Connection; }
	if ( Result == MAXSI_ERROR_SUCCESS ) { *Socket = Connection; }

	return Result;
}

MaxsiError MaxsiIODeviceEpoll::AcceptClient(MaxsiIOHandleEpoll* Handle)
{
	Handle->Connection->NotifyIncomingConnection((MaxsiIOSocket*)Handle, 0);

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiIODeviceEpoll::DoRead(MaxsiIOHandleEpoll* Handle)
{
	Handle->Connection->NotifyRead(Handle, MAXSI_IO_READ_POSSIBLE);

	return MAXSI_ERROR_SUCCESS;
	
}

MaxsiError MaxsiIODeviceEpoll::DoWrite(MaxsiIOHandleEpoll* Handle)
{
	Handle->Connection->NotifyWrite(Handle, MAXSI_IO_WRITE_POSSIBLE);

	return MAXSI_ERROR_SUCCESS;	
}


MaxsiError MaxsiIODeviceEpoll::HandleDisconnect(MaxsiIOHandle* Connection, MaxsiError* Error)
{
	return MAXSI_ERROR_NOT_IMPLEMENTED;
}

#endif

